Image Processing 3

Table of Contents

This assignment is to be done individually. You can talk to other people in the class, me (Dave), the prefect, and lab assistants for ideas and to gain assistance. You can help each other debug programs, if you wish. The code that you write should be your own, however, and you shouldn’t hand a printout of your program to others. See the course syllabus for more details or just ask me if I can clarify.

1. Get started

Create a folder in which to store your work for this assignment.

  • If you are working on your own computer, it’s up to you where to put the folder. Your desktop is likely as good a place as any. Make a folder titled image3.
  • If you are working in the labs in Olin, make sure to first mount the COURSES folder, so that you won’t lose your code when you log out. Once you’ve done so, open up Finder, then navigate to your personal student work folder. You can then make a image3 folder within there.
  • Once you’ve done so, you should then open up your new folder in VS Code. To do so, start up VS Code, then drag your folder onto the VS Code window. This should open up the folder within VS Code. If you are asked, click that you trust the authors.

To facilitate reading in, storing, and manipulating image files in “standard” formats (like .jpg, .gif, and .png), we will again use images.py, which is included in your repl. Here is the documentation for images.py.

2. Your assignment

In a Python module called photolab3.py, write the following functions:

2.1. Scaling the image

Create a Python function defined as:

scale(image, scaling)

Scales your image. If scaling is 2, for example, each side of your new image should be double the size of the original. Likewise, if scaling is 0.25, each side of your new image should be 1/4 the size of the original. If scaling is less than or equal to 0, do no image work at all and return None. Otherwise, return the new image.

You’ve got a choice to make. Do you loop over all the pixels in the old image, and figure out where they belong in the new image? Or do you loop over all the pixels in the new image (which are initially unassigned), and for each one figure out which is the right pixel in the old image to copy in? One of these approaches is much easier than the other. I’ve got advice about this in the videos in 2d image processing.

In order to do this exercise, you’ll need to do arithmetic that will have non-integer answers. So that everyone ends up with consistent results, you should avoid rounding off until the very last minute, i.e., when you actually need a particular pixel. At that point, you should always truncate, i.e., round down. You’ll need to do this to make sure your results match mine.

FAQ: What if the new image is bigger than the original? Do you need to interpolate between pixels to calculate appropriate pixel values and so on?

Answer: No. Every pixel on the new image should just be a copy of some pixel on the old. The challenge is in figuring out which one. If the new image is smaller than the old, then some of the pixels on the original will be ignored. If the new image is larger than the old, than some of the pixels from the original will be duplicated.

2.2. Blurring the image

Create a Python function defined as:

blur(image, radius)

Blurs an image. For each pixel, average all of the pixels within a square centered at that pixel and with a “radius” as given. In other words, if your radius is 3, your square should go 3 pixels left, 3 pixels right, 3 pixels up, and 3 pixels down from the center to form a 7 x 7 square. (Note that this is different from the blurring I did in my examples, which just did it in a line.) Make sure to put all of your results in a new image, instead of overwriting your original as you go; otherwise, your blurred pixels will cascade on top of each other.

You’ll want to work with a small image for this task, as blurring in Python can take some time. Here is picture of Budapest which is nice and small. Nonetheless, some approaches to programming this result in much slower algorithms than others. On my home desktop computer, I can blur this small image in about 3-5 seconds with a radius of 3 and about 15 seconds with a radius of 8. In order to get an M grade for this task, you must be able to blur this image, with a radius of 8, on a lab computer, in under 30 seconds.

You should ignore blurring around the edges of the image that is in the range where the blurring square would run off the edge of the image. Leave those pixels in the image so that the blurred image is the same size as the original, but all pixels that can’t be completely blurred should be remained intact. For example, if the radius is 3, then the first three pixels from any side of the image should not be blurred at all.

3. Test your code

There are no testing scripts for this assignment; instead, you’ll have to look at the images you produce, and see if they look right. The graders will do similarly.

4. Design and style

Make sure that your program follows good design and style. See the guidelines from recent assignments. If you really want to learn lots about Python style, the official style guide for Python is a great read.

5. Exemplary

If you complete the above successfully (and have good style in your code), you will receive nearly all the points for the assignment. If you get this far, you should feel proud of your achievements! If you want to push yourself harder and go for the exemplary grade, do the following extra challenge.

dither(image)

Dithering is a technique used when you want to print a gray picture in a place such as a newspaper where no shades are available. Instead, you need to use individual pixels of black and white to simulate shades of gray. The popular Floyd-Steinberg algorithm for dithering is as follows:

  • Loop over all pixels, running across one row at a time.
  • For each pixel: if its value is larger than 128, set it to 255 (pure white). Otherwise, set it to 0 (pure black). Record the “error”, which is the old value of this pixel minus the new one.
  • This “error” represents how much blackness we have added to this pixel. Distribute this blackness to adjacent pixels as follows:
    1. Add 7/16 of the error to the pixel immediately to the right (“east”).
    2. Add 3/16 of the error to the pixel immediately diagonally to the bottom left (“southwest”).
    3. Add 5/16 of the error to the pixel immediately below (“south”).
    4. Add 1/16 of the error to the pixel immediately diagonally to the bottom right (“southeast”).
  • Any error to be distributed to pixels that are out of range should be ignored.

Dithering should only be done on gray images, so convert the image to gray first inside your dither function. You can do this by averaging the red/green/blue values, and setting that average for all three colors, or you can use some built-in methods in the images library to help. Here is a sample function that converts a image to gray.

# If you use this sample code, make sure to grab a new copy of images.py, as linked by this
# assignment. I've added some additional code to it to help with gray images.
def makeGray(original):

    # Construct a new empty image to hold the result
    result = EmptyImage(original.getWidth(), original.getHeight())

    for i in range(original.getWidth()):
        for j in range(original.getHeight()):
            originalPixel =original.getPixel2D(i, j)

            # Get a number representing the gray value of the pixel. This is automatically
            # determined to be the average of the red/green/blue values
            grayValue = originalPixel.getGray()

            # Set the corresponding pixel in the new image to be this gray value. By
            # specifiying Pixel(gray=grayValue), the library automatically sets the
            # red/green/blue values all to be equal to grayValue. It's exactly the same as
            # Pixel(grayValue, grayValue, grayValue), but with a little less typing.
            result.setPixel2D(i, j, Pixel(gray=grayValue))

    return result

In order for dithering to work, you must make your changes to the same image that you are looping over. Dithering by reading one image and making your changes on a copy will not work correctly, because the “error” never gets a chance to accumulate. In other words, make sure that you make a gray copy of the image first, and then do all dithering work (looping, reading, writing, etc.) on the copy itself. If your image looks really weird, this may likely your problem. But remember, you should not change the original image that is passed to this function.

dithered.png

6. Grading

You will receive an M for this assignment if…

  • when we run your functions, the images that result appear correct.
  • your program is written to work in general, and not to only work for the specific tests that we have provided.

You will receive a grade of E for this assignment if you satisfy the above M requirements, and …

  • your programs have a comment at the top of each function with at least 5 words describing what the function does.
  • every one of your variable names is meaningful in some way. (Names such as thing, number, etc. are not meaningful.)
  • You have at least one other comment near what you think is the trickiest part of your code, describing how it works
  • your code demonstrates a clear sequence of actions to achieve the goal at hand, and each piece is essential. Your code does not have notably more cases or conditions than it needs to.
  • you have successfully achieved the criteria described in the exemplary section above

7. Submit your work

When finished, zip up your code and submit your work through Moodle.

Good luck, and have fun! Remember that lab assistants are available to help out if you need it, and you can attend prefect sessions as well.

Author: Dave Musicant